// Gtk+ implementation of rating widget, using RbRating from Rhythmbox.
// Copyright 2006 (C) Ralph Thomas

#include <view/rating.h>
#include <rb-rating.h>
#include <rb-stock-icons.h>
#include <gtk/os_utilities.hpp>
#include <binder.hpp>
#include <iostream>
#include <sstream>
#include <util/resources.h>

namespace bfs = boost::filesystem;

namespace view {
	struct rating_t {
		typedef adobe::implementation::signal_wrapper_one_t<void, double> rated_t;
		adobe::auto_ptr<GtkWidget>	control_m;
		adobe::auto_ptr<GtkWidget>	label_m;
		rated_t				on_rate_m;
		adobe::binder_helper_t		datapump_m;
		bool				in_string_mode_m; ///< If we get set with a string then return a string.
		bool				using_label_m;

		void on_rate( double value ) {
			adobe::any_regular_t outval;

			if( in_string_mode_m ) {
				std::stringstream ss;
				ss << value;
				outval = adobe::any_regular_t( ss.str() );
			} else outval = adobe::any_regular_t( value );
			datapump_m.set( outval );
			g_object_set( G_OBJECT(control_m.get()), "rating", value, NULL );
		}
		void update( const adobe::any_regular_t& value ) {
			double outval;
			if( value.type() == typeid( std::string ) ) {
				in_string_mode_m = true;
				std::string string_value( value.cast<std::string>() );
				std::stringstream sstream( string_value );
				sstream >> outval;
				if( string_value == std::string() ) outval = 0.0;
			} else {
				try { outval = value.cast<double>(); } catch( ... ) { return; }
			}
			if( outval < 0.0 || outval > 5.0 ) return;
			g_object_set( G_OBJECT(control_m.get()), "rating", outval, NULL );
		}
		
		rating_t( adobe::sheet_t& sheet, const adobe::any_regular_t& bind, const std::string& name, const std::string& style ) : datapump_m( sheet ), in_string_mode_m( false ), using_label_m( false ) {
			//
			// Make sure that we have the extra icons required for the rating widget
			// loaded.
			//
			bfs::path icon_path = util::getResourcePath() / "images";
			rb_stock_icons_init( icon_path.native_file_string().c_str() );
			control_m.reset( GTK_WIDGET( rb_rating_new() ) );
			//
			// Set the style (if we have one).
			// 
			if( !style.empty() ) gtk_widget_set_name( control_m.get(), style.c_str() );
			//
			// If we have a name then create a label widget for it.
			//
			if( !name.empty() ) {
				label_m.reset( gtk_label_new( name.c_str() ) );
				using_label_m = true;
			}
			//
			// Connect to the data pump.
			//
			datapump_m.initialize( bind, boost::bind( &rating_t::update, this, _1 ),
				boost::bind( &gtk_widget_set_sensitive, control_m.get(), _1 ) );
			//
			// Hook-up to the "rated" signal.
			//
			on_rate_m.set_callback( boost::bind( &rating_t::on_rate, this, _1 ) );
			on_rate_m.connect( control_m.get(), "rated" );
		}

		void measure(adobe::extents_t& extents) {
			adobe::extents_t base_extents( adobe::implementation::best_bounds_widget( control_m.get() ) );
			if( !using_label_m ) { extents = base_extents; return; }
			extents = adobe::implementation::best_bounds_widget( label_m.get() );
			extents.horizontal().guide_set_m.push_back( extents.width() );
			extents.width() += base_extents.width();
			extents.height() = std::max( extents.height(), base_extents.height() );
		}
		void place( const adobe::place_data_t& place_data ) {
			if( !using_label_m || place_data.horizontal().guide_set_m.empty() ) {
				adobe::implementation::set_bounds_widget( control_m.get(), place_data );
				return;
			}
			//
			// Calculate the sizes and positions based on the location of
			// the horizontal point-of-interest (adjusted by Eve).
			//
			adobe::place_data_t pos( place_data );
			pos.horizontal().length_m = pos.horizontal().guide_set_m[0];
			adobe::implementation::set_bounds_widget( label_m.get(), pos );
			pos.horizontal().position_m += place_data.horizontal().length_m;
			pos.horizontal().length_m = place_data.horizontal().length_m - pos.horizontal().length_m;
			adobe::implementation::set_bounds_widget( control_m.get(), pos );
		}

	};
	adobe::widget_node_t rating_factory(
		const adobe::dictionary_t&	parameters,
		const adobe::widget_node_t&	parent,
		const adobe::factory_token_t&	token )
	{
		//
		// "bind" is mandatory.
		//
		adobe::any_regular_t bind = parameters.at( adobe::static_name_t( "bind" ) );
		std::string name;
		if( parameters.count( adobe::static_name_t( "name" ) ) )
			name = parameters.get<std::string>( adobe::static_name_t( "name" ) );
		std::string style;
		if( parameters.count( adobe::static_name_t( "style" ) ) )
			style = parameters.get<std::string>( adobe::static_name_t( "style" ) );
		//
		// Make the widget.
		//
		rating_t* rating( new rating_t( token.sheet_m, bind, name, style ) );
		token.client_holder_m.assemblage_m.delete_on_destruction( rating );
		//
		// Insert the widget into the given parent.
		//
		GtkWidget* ref = rating->control_m.get();
		token.display_m.insert( parent.display_token_m, ref );
		if( rating->using_label_m ) {
			GtkWidget* label = rating->label_m.get();
			token.display_m.insert( parent.display_token_m, label );
		}
		//
		// Bind the calculate and place signals for Eve.
		//
		adobe::any_placeable_t* p(new adobe::any_placeable_t(boost::ref(*rating)));
		//
		// Register the widget with Eve.
		//
		adobe::eve_t::iterator eve_token(
			token.client_holder_m.eve_m.add_view_element( parent.eve_token_m,
				adobe::layout_attributes_t(),
				false,
				parameters,
				boost::ref(*p) )
			);
		//
		// Return the new rating widget.
		//
		return adobe::widget_node_t( parent.size_m, eve_token, ref, parent.keyboard_token_m );
	}
};

